
(function(){

  var namespace = 'App.Component';

  //
  // import names
  //

  var classList = Basis.CSS.classList;
  var DOM = Basis.DOM;
  var nsEvent = Basis.Event;
  var nsData = Basis.Data;
  var nsWrapper = Basis.DOM.Wrapper;
  var nsButton = Basis.Controls.Button;


  //
  // Const
  //

  var TYPE_TITLE = {
    Block: 'Блок',
    Elem: 'Элемент',
    Mod: 'Модификатор'
  };
  var SIZE_LABEL = ['bytes', 'Kb', 'Mb'];

  var hasTechDataset = new nsData.Dataset.MapReduce({
    map: function(tech){
      return App.Type.TechType(tech.getName());
    }
  });
  var missedTechDataset = new nsData.Dataset.Subtract({
    minuend: App.Type.TechType.all,
    subtrahend: hasTechDataset
  });

  //
  // Selection functions
  //
  function setSelectionPos(node, pos){
    var selection = window.getSelection();
    var range = document.createRange();
    var pos = pos || 0;
    var firstChild = node.element.firstChild;
    range.setStart(firstChild, Math.min(firstChild.length, pos));
    selection.removeAllRanges();
    selection.addRange(range);
  }

  function insertText(node, text){
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    var startContainer = range.startContainer;

    if (!range.collapsed)
      range.deleteContents();

    startContainer.insertData(range.startOffset, text);
    setSelectionPos(node, range.startOffset + text.length);
  }

  function getSelectionTextBefore(){
    var selection = window.getSelection();
    var range = selection.getRangeAt(0);
    return range.startContainer.nodeValue.substring(0, range.startOffset);
  }

  //
  // File utils
  //

  function fileLoaded(event){
    var reader = event.target;
    var fileContent = reader.result;

    //if (reader.isBinary)
    //  fileContent = Basis.Crypt.Base64.encode(Array.from(fileContent).map(function(chr){ return chr.charCodeAt(0) }));

    reader.tech.update({
      content: fileContent
    });
  };

  function formatSize(size){
    var count = 0;
    while (size > 1024 && count++ < SIZE_LABEL.length)
      size /= 1024;
    return {
      number: parseInt(size * 100) / 100,
      unit: SIZE_LABEL[count]
    }
  };


  //
  // Tech editors
  //

 /**
  * @class
  */
  var TechEditor = nsWrapper.TmplNode.subclass({
    template: 
      '<div tabindex="0" class="tech-editor"' +
       ' event-blur="focusout"' +
       ' event-focus="focusin"/>',

    action: {
      focusin: function(){
        this.focused = true;
        App.UrlResolver.setTarget(this.target.getId());
      },
      focusout: function(){
        this.focused = false;
        if (this.target)
          App.UrlResolver.setTarget(this.target.getParent().getId());
      }/*,
      filedrag: function(event){
        event.dataTransfer.setData('text/plain',
          'text/plain' +
          ':test.css' +
          ':http://localhost/bem_editor/bem_editor/app/_style/main.css'
          //':data:text/plain;base64,' + Basis.Crypt.Base64.encode(this.data.content)
        );
      }*/
    },

    init: function(config){
      this.delegate = this.owner;
      nsWrapper.TmplNode.prototype.init.call(this, config);
    },

    focus: function(){
      DOM.focus(this.element);
    }
  });

 /**
  * @class
  */
  TechEditor.Text = TechEditor.subclass({
    template:
      '<div class="tech-editor tech-editor_text"' +
        ' event-blur="focusout storePos save"' +
        ' event-focus="focusin restorePos"' +
        ' event-keypress="filter"' +
        //' event-mouseup="storePos"' +
        //' event-mousedown="storePos"' +
        ' event-keydown="storePos"' +
        ' event-keyup="storePos"' +
        ' event-paste="storePos"' +
        ' event-input="storePos checkDrop"' +

        ' event-dragenter="dragenter"' +
        ' event-dragleave="dragleave"' +
        ' event-dragover="dragover"' +
        ' event-dragend="dragend"' +
        ' event-drop="drop"' +
      '/>',

    draginput: 0,

    action: {
      filter: function(event){
        var key = nsEvent.key(event);
        if (key == nsEvent.KEY.ENTER || key == nsEvent.KEY.CTRLENTER)
        {
          // prevent defaults
          nsEvent.kill(event);

          // on enter insert the same space count as at current line
          insertText(this, '\n' + (getSelectionTextBefore().split(/\n/).pop()).match(/^\s*/)[0]); // 
        }
      },
      restorePos: function(){
        this.focus();
      },
      save: function(){
        this.save();
      },
      storePos: function(){
        var selection = window.getSelection();
        if (selection && selection.rangeCount)
        {
          this.update({
            lastPos: selection.getRangeAt(0).startOffset
          });
        }
      },

      dragover: function(event){
        //classList(this.element).add('drag-over');
        //nsEvent.kill(event);
        //console.log(event);
      },
      dragenter: function(){
        this.draginput += 1;
        console.log('enter');
        if (this.draginput)
          classList(this.element).add('drag-over');
        this.focus();
      },
      dragleave: function(){
        this.draginput += -1;
        console.log('leave');
        classList(this.element).remove('drag-over');
      },
      checkDrop: function(){
        console.log('check input');
        if (this.draginput)
          classList(this.element).remove('drag-over');
        this.draginput = 0;
      },
      dragend: function(){
        console.log('dropend');
      },
      drop: function(){
        console.log('drop');
        classList(this.element).remove('drag-over');
      }
    },

    handler: {
      update: function(object, delta){
        if ('content' in delta)
          DOM.insert(DOM.clear(this.element), this.data.content);
      }
    },

    // methods

    focus: function(){
      setSelectionPos(this, this.data.lastPos);
    },
    save: function(){
      this.removeHandler(this.handler);
      this.update({
        content: DOM.textContent(this.element)
      });
      this.addHandler(this.handler);
    },
    destroy: function(){
      this.save();
      
      nsWrapper.TmplNode.prototype.destroy.call(this);
    }
  });

 /**
  * @class
  */
  TechEditor.Image = TechEditor.subclass({
    template:
      '<div{content} tabindex="0" class="tech-editor tech-editor_image"' +
       ' event-focus="focusin"' +
       ' event-blur="focusout">' +
        '<img{image} class="tech-editor__image"/>' +
      '</div>',

    handler: {
      update: function(object, delta){
        if ('content' in delta)
          this.tmpl.image.src = this.data.content;
      }
    }
  });

  //
  // BemDecl editor
  //

 /**
  * @class
  */
  TechEditor.BemDecl = TechEditor.subclass({
    template:
      '<div{content} tabindex="0" class="tech-editor tech-editor_bemdecl"' +
       ' event-focus="focusin"' +
       ' event-blur="focusout">' +
        '<!-- {bemeditor} -->' +
      '</div>',

    satelliteConfig: {
      bemeditor: (function(){ // make closure here for internal classes declaration

        var BemDeclMods = nsWrapper.TmplContainer.subclass({
          template:
            '<span class="bem-editor-mods"/>'/*,

          childClass: nsWrapper(
            '<span> {this_data_name} </span>'
          )*/
        });

       /**
        * Base child node class for editor
        * @class
        */
        var BemDeclNode = nsWrapper.TmplContainer.subclass({
          template:
            '<div class="bem-editor-node">' +
              '<span class="bem-editor-node__title">{titleText}</span>' +
              '<!-- {mods} -->' +
            '</div>',

          event_update: function(object, delta){
            nsWrapper.TmplContainer.prototype.event_update.call(this, object, delta);

            this.tmpl.titleText.nodeValue = this.data.name;
            if ('elems' in delta)
              this.setChildNodes(this.data.elems);
          },

          satelliteConfig: {
            mods: {
              existsIf: Function.getter('data.mods'),
              instanceOf: BemDeclMods,
              config: function(owner){
                return {
                  childNodes: owner.data.mods
                }
              }
            }
          }
        });

       /**
        * @class
        */
        BemDeclNode.Elem = BemDeclNode.subclass({
          cssClassName: 'bem-editor-node_elem'
        });

       /**
        * @class
        */
        BemDeclNode.Block = BemDeclNode.subclass({
          cssClassName: 'bem-editor-node_block',
          childClass: BemDeclNode.Elem
        });

       /**
        * @class
        */
        BemDeclNode.Mod = BemDeclNode.subclass({
          cssClassName: 'bem-editor-node_mod'
        });

        BemDeclMods.prototype.childClass = BemDeclNode.Mod;

        //
        // satellite config
        //
        return {
          instanceOf: nsWrapper.TmplContainer.subclass({
            template:
              '<div class="bem-editor"/>',

            childClass: BemDeclNode.Block,

            init: function(config){
              this.delegate = this.owner;
              nsWrapper.TmplContainer.prototype.init.call(this, config);
            },

            handler: {
              update: function(object, delta){
                if ('content' in delta)
                {
                  // convert content to object
                  var rawData = this.data.content.replace(/^\s*exports\.blocks\s*=\s*/, '').toObject() || [];
                  var data = [];

                  function buildMods(mods){
                    var result = {};

                    for (var i = 0, mod; mod = mods[i++];)
                      for (var j = 0; j < mod.vals.length; j++)
                      {
                        var val = mod.vals[j];
                        result[mod.name + (val ? '_' + val : '')] = true;
                      }

                    return Object.keys(result).map(function(name){
                      return {
                        data: {
                          name: name
                        }
                      }
                    });
                  }

                  // filter unnecessary properties
                  for (var i = 0, block; block = rawData[i++];)
                  {
                    if (block.mods)
                      block.mods = buildMods(block.mods);

                    if (block.elems)
                      block.elems = block.elems.map(function(elem){
                        if (elem.mods)
                          elem.mods = buildMods(elem.mods);
                        return {
                          data: elem
                        };
                      });

                    data.push({
                      data: block
                    });
                  }

                  // apply new data
                  this.setChildNodes(data);
                }
              }
            }
          })
        };
      })()
    }
  });


  //
  // Tech info panel
  //

 /**
  * @class
  */
  var TechInfoPanel = nsWrapper.TmplNode.subclass({
    init: function(config){
      this.delegate = this.owner;
      nsWrapper.TmplNode.prototype.init.call(this, config);
    }
  });

 /**
  * @class
  */
  TechInfoPanel.Image = TechInfoPanel.subclass({
    template:
      '<div class="tech-infobar">' +
        '<span class="tech-infobar__item"><span class="tech-infobar__value">{infoSizeText}</span>{infoUnitText}</span>' +
        '<span{imageDim} class="tech-infobar__item">, <span class="tech-infobar__value">{infoWidthText}</span>x<span class="tech-infobar__value">{infoHeightText}</span></span>' +
      '</div>',

    handler: {
      update: function(object, delta){
        var tmpl = this.tmpl;

        if ('content' in delta)
        {
          DOM.hide(tmpl.imageDim);
          if (this.data.content)
          {
            if (!this.testImage)
            {
              this.testImage = new Image();
              this.testImage.onload = function(){
                DOM.show(tmpl.imageDim);
                tmpl.infoWidthText.nodeValue = this.naturalWidth;
                tmpl.infoHeightText.nodeValue = this.naturalHeight;
              };
            }

            this.testImage.src = this.data.content;
          }
        }

        if ('size' in delta)
        {
          var size = formatSize(this.data.size || 0);
          tmpl.infoSizeText.nodeValue = size.number;
          tmpl.infoUnitText.nodeValue = size.unit;
        }
      }
    }
  });

  //
  // Tech toolbar
  //

 /**
  * @class
  */
  var TechToolbar = nsButton.ButtonPanel.subclass({
    template:
      '<div class="tech-toolbar"/>',

    init: function(config){
      this.delegate = this.owner;
      nsWrapper.TmplNode.prototype.init.call(this, config);
    },

    childClass: {
      autoDelegateParent: true
    }
  });


  //
  // Tech viewers
  //

 /**
  * @class
  */
  var TechView = nsWrapper.TmplNode.subclass({
    className: namespace + '.TechView',

    template:
      '<div class="tech-view">' +
        '<div class="tech-view__header">' +
          '<div class="tech-view__title">{typeText}</div>' +
          '<span class="tech-view__delete-button" event-click="destroy">Удалить</span>' +
          '<!-- {toolbar} -->' +
          '<!-- {infobar} -->' +
        '</div>' +
        '<!-- {editor} -->' +
      '</div>',

    action: {
      destroy: function(){
        App.parser.run('delete tech ' + this.target.getName() + ' ' + this.target.getParent().getCmdPath());
      }
    },

    init: function(config){
      nsWrapper.TmplNode.prototype.init.call(this, config);
      this.tmpl.typeText.nodeValue = this.target.getName();
    },

    focus: function(){
      var editor = this.satellite.editor;

      if (this.element.scrollIntoViewIfNeeded)
        this.element.scrollIntoViewIfNeeded(true);

      if (editor)
        editor.focus();
    }
  });

 /**
  * @class
  */
  TechView.Text = TechView.subclass({
    className: namespace + '.TechView.Text',

    satelliteConfig: {
      editor: TechEditor.Text
    }
  });

 /**
  * @class
  */
  TechView.Html = TechView.Text.subclass({
    satelliteConfig: {
      toolbar: {
        instanceOf: TechToolbar,
        config: {
          childNodes: [
            {
              caption: 'Создать декларацию',
              click: function(){
                App.parser.run('create bemdecl ' + this.target.getCmdPath());
              }
            }
          ]
        }
      }
    }
  });

 /**
  * @class
  */
  TechView.BemDecl = TechView.subclass({
    satelliteConfig: {
      editor: TechEditor.BemDecl
    }
  });

 /**
  * @class
  */
  TechView.Image = TechView.subclass({
    className: namespace + '.TechView.Image',

    satelliteConfig: {
      infobar: TechInfoPanel.Image,
      editor: TechEditor.Image
    }
  });

  //
  // Main part
  //

  // build map tech type -> viewer
  var techTypeViewClass = {
    gif: TechView.Image,
    png: TechView.Image,
    jpg: TechView.Image,
    html: TechView.Html,
    'bemjson.js': TechView.Html,
    'bemdecl.js': TechView.BemDecl
  };
  App.Type.TechType.all.getItems().forEach(function(tt){
    if (!techTypeViewClass[tt.data.id])
      techTypeViewClass[tt.data.id] = TechView.Text;
  });

 /**
  */
  var viewerList = new nsWrapper.TmplContainer({
    id: 'TechListViewer',
    autoDelegateParent: true,

    template:
      '<div class="TechListViewer" event-dragenter="dragenter" event-dragleave="dragleave" event-dragover="dragover" event-drop="drop">' +
        '<!-- {childNodesHere} -->' +
      '</div>',

    action: {
      dragover: function(event){
        classList(this.element).add('drag-over');
        nsEvent.kill(event);
      },
      dragenter: function(event){
        classList(this.element).add('drag-over');
      },
      dragleave: function(event){
        classList(this.element).remove('drag-over');
      },
      drop: function(event){
        // drag over - remove class
        classList(this.element).remove('drag-over');

        // fetch files
        var files = event.dataTransfer.files;
        if (!files || !files.length)
          return;  // there are no files

        // process files
        for (var i = 0; i < files.length; i++)
        {
          var file = files[i];
          var ext = file.name.toLowerCase().match(/\.([\w\.]+)$/);
          var techType = ext && App.Type.TechType.get(ext[1]);
          var techId = this.data.id + ext[0];

          // tech type recognized
          if (techType)
          {
            // create/update tech
            var tech = App.Type.Tech({
              id: techId,
              mimeType: file.type,
              size: file.size
            });

            // create file reader to fetch file content
            var reader = new FileReader();
            reader.onload = fileLoaded;
            reader.tech = tech;

            // read file content
            if (techType.data.isBinary)
              reader.readAsDataURL(files[i]);
            else
              reader.readAsText(files[i]);
          }
        }

        //if (techType)
        //  this.focusTech(techType);
      }
    },

    handler: {
      update: function(object, delta){
        if ('id' in delta)
          this.setDataSource(App.Type.Tech.splitByOwner.getSubset(this.data.id, true));
      },
      dataSourceChanged: function(){
        hasTechDataset.setSource(this.dataSource);
      }
    },

    childClass: TechView,
    childFactory: function(config){
      var childClass = techTypeViewClass[config.delegate.getName()] || TechView;
      return new childClass(config);
    },
    localSorting: App.Type.TechType.sorting,
    focusTech: function(tech){
      var techNode = this.childNodes.search(tech, 'target.getName()');
      if (techNode && !techNode.focused)
        techNode.focus();
    }
  });

  new Basis.Controls.Label.IsEmpty({
    delegate: viewerList,
    content: 'Нет технологий'
  });

 /**
  * Header
  */
  var viewerHeader = new nsWrapper.TmplContainer({
    id: 'TechListHeader',
    autoDelegateParent: true,

    template:
      '<div>' +
        '<h1>' +
          '<span class="type">{typeText}</span>' +
          '<span{path} class="path"></span>{titleText}' +
        '</h1>' +
        '<div class="TechListViewer-Toolbar">' +
          'Добавить технологию: <!-- {newTechList} -->' +
        '</div>' +
      '</div>',

    handler: {
      targetChanged: function(){
        var target = this.target;

        if (target)
        {
          var path = (App.Type.split2parts(target.getId()).slice(0, -1).map(function(path){
            return path.replace(/^(__?|\/)/, '')
          }).join('/') + '/').split(/(\/)/);
          DOM.insert(DOM.clear(this.tmpl.path), DOM.wrap(path, { 'SPAN.splitter': function(part, index){ return index % 2 } }));

          this.tmpl.titleText.nodeValue = target.getName();
          this.tmpl.typeText.nodeValue = TYPE_TITLE[target.typeName];
        }
      }
    },

    satelliteConfig: {
      newTechList: {
        delegate: Function.$self,
        instanceOf: nsWrapper.TmplContainer.subclass({
          cssClassName: 'TechListViewer-TechTypeList',
          dataSource: missedTechDataset,
          localSorting: Function.getter('data.order'),
          childClass: {
            template:
              '<span class="TechListViewer-CreateTechButton" event-click="create">' +
                '{titleText}' +
              '</span>',

            action: {
              create: function(event){
                //location.hash = App.Type.Tech.create(this.parentNode.data.id + '.' + this.data.id).getId();
                App.parser.run('create tech ' + this.target.getId() + ' ' + this.parentNode.target.getCmdPath());
              }
            },

            handler: {
              update: function(object, delta){
                this.tmpl.titleText.nodeValue = this.data.title;
              }
            }
          }
        })
      }
    }
  });


 /**
  * Module layout
  */
  var panel = new Basis.Layout.VerticalPanelStack({
    id: 'TechViewPanel',
    childNodes: [
      {
        id: 'TechListHeaderPanel',
        autoDelegateParent: true,
        childNodes: [
          viewerHeader
        ]
      },
      {
        id: 'TechListViewerPanel',
        autoDelegateParent: true,
        flex: 1,
        childNodes: [
          {
            template:
              '<iframe id="WelcomeScreen" src="app/_static/welcome.html"/>'
          },
          viewerList
        ]
      }
    ],
    handler: {
      targetChanged: function(){
        classList(this.element).bool('HasTarget', !!this.target);
      }
    }
  });

  //
  // listen for hash changes
  //
  App.UrlResolver.addHandler({
    targetChanged: function(path){
      var object = App.Type.resolveBem(path);
      var tech;
      if (object)
      {
        if (object.type === App.Type.Tech)
        {
          tech = object.getName();
          object = object.getParent();
        }
      }

      panel.setDelegate(object);
      if (tech)
        viewerList.focusTech(tech);
    }
  });

  //
  // export names
  //
  Basis.namespace(namespace).extend({
    TechListViewer: panel
  });

})();